package com.fujimana.rockincat.restree.init;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.fujimana.rockincat.restree.converter.Binder;
import com.fujimana.rockincat.restree.converter.Converter;
import com.fujimana.rockincat.restree.filter.DelegateFilter;
import com.fujimana.rockincat.restree.init.binder.BinderItem;
import com.fujimana.rockincat.restree.init.converter.ConverterItem;
import com.fujimana.rockincat.restree.init.filter.FilterItem;
import com.fujimana.rockincat.restree.init.message.MessageItem;
import com.fujimana.rockincat.restree.init.method.MethodItem;
import com.fujimana.rockincat.restree.init.resource.ResourceItem;
import com.fujimana.rockincat.restree.message.MessageManager;
import com.fujimana.rockincat.restree.message.MessageRepository;
import com.fujimana.rockincat.restree.proxy.MethodMetaData;
import com.fujimana.rockincat.restree.proxy.ResourceMetaData;
import com.fujimana.rockincat.restree.proxy.ResourceMetaDataFactory;
import com.fujimana.rockincat.restree.proxy.ResourceProxy;
import com.fujimana.rockincat.restree.proxy.ResourceProxyFactory;
import com.fujimana.rockincat.restree.util.ClassUtils;

public class Init {

	private static final String METHOD_ITEM_TYPE = MethodItem.class.getName();

	private static final String CONVERTER_ITEM_TYPE = ConverterItem.class.getName();
	
	private static final String BINDER_ITEM_TYPE = BinderItem.class.getName();

	private static final String RESOURCE_ITEM_TYPE = ResourceItem.class
			.getName();

	private static final String FILTER_ITEM_TYPE = FilterItem.class.getName();

	private static final String MESSAGE_ITEM_TYPE = MessageItem.class.getName();

	private static final String CONFIGURE_XML = "/WEB-INF/restree.xml";

	private Set<MethodMetaData> methods;

	private Map<String, Converter> converters;

	private Map<String, Binder> binders;

	private Map<String, ResourceMetaData> resource;

	private Set<String> endpoints;

	private List<Filter> filters;

	private List<String> supportedCharset;

	private List<Locale> supportedLocal;

	private List<String> supporttedRequestContentType;

	private List<String> supporttedResponseContentType;

	private List<String> messageResources;

	private MessageRepository messageRepository;

	public MessageRepository getMessageRepository() {
		return messageRepository;
	}

	public List<String> getMessageResources() {
		return messageResources;
	}

	public void setMessageResources(List<String> messageResources) {
		this.messageResources = messageResources;
	}

	public List<String> getSupporttedRequestContentType() {
		return supporttedRequestContentType;
	}

	public List<String> getSupporttedResponseContentType() {
		return supporttedResponseContentType;
	}

	public void setSupporttedResponseContentType(
			List<String> supporttedResponseContentType) {
		this.supporttedResponseContentType = supporttedResponseContentType;
	}

	public void setSupporttedRequestContentType(
			List<String> supporttedRequestContentType) {
		this.supporttedRequestContentType = supporttedRequestContentType;
	}

	public List<Locale> getSupportedLocal() {
		return supportedLocal;
	}

	public void setSupportedLocal(List<Locale> supportedLocal) {
		this.supportedLocal = supportedLocal;
	}

	public List<String> getSupportedCharset() {
		return supportedCharset;
	}

	public void setSupportedCharset(List<String> supportedCharset) {
		this.supportedCharset = supportedCharset;
	}

	public List<Filter> getFilters() {
		return filters;
	}

	public Map<String, ResourceMetaData> getResource() {
		return Collections.unmodifiableMap(resource);
	}

	public Init() {
		this.methods = new HashSet<MethodMetaData>();
		this.converters = new HashMap<String, Converter>();
		this.binders = new HashMap<String, Binder>();
		this.resource = new HashMap<String, ResourceMetaData>();
		this.endpoints = new HashSet<String>();
		this.filters = new ArrayList<Filter>();

		this.supportedCharset = new ArrayList<String>();
		this.supportedLocal = new ArrayList<Locale>();
		this.supporttedRequestContentType = new ArrayList<String>();
		this.supporttedResponseContentType = new ArrayList<String>();

		this.messageResources = new ArrayList<String>();
	}

	private List<Filter> sortFilter(List<Filter> filters) {
		return filters;
	}

	private List<Item> sortItem(List<Item> items) {

		Collections.sort(items, new Comparator<Item>() {

			@Override
			public int compare(Item o1, Item o2) {

				if (o1.getType().equals(RESOURCE_ITEM_TYPE)) {
					return 1;
				}

				return 0;
			}
		});

		return items;
	}

	public void scan(ClassLoader classLoader) {
		ClassFileDeployer deployer = new ClassFileDeployer();

		List<Item> items = sortItem(deployer.scan());

		for (Item item : items) {
			String type = item.getType();
			if (type.equals(METHOD_ITEM_TYPE)) {
				String method = ((MethodItem) item).getMethod().toLowerCase();
				Class<? extends Annotation> anno = ((MethodItem) item)
						.getClazz();
				
				if (this.methods.contains(method)) {
					throw new InitializedException("TODO");
				}

				this.methods.add(new MethodMetaData(anno, method));

			} else if (type.equals(CONVERTER_ITEM_TYPE)) {
				Converter converter = (Converter) ClassUtils
						.newInstance(((ConverterItem) item).getConverter());
				String mime = ((ConverterItem) item).getMimeType().toLowerCase();

				if (this.converters.containsKey(mime)) {
					throw new InitializedException("TODO");
				}

				this.converters.put(mime, converter);
			} else if (type.equals(BINDER_ITEM_TYPE)) {
				Binder binder = (Binder) ClassUtils
						.newInstance(((BinderItem) item).getBinder());
				String mime = ((BinderItem) item).getMimeType().toLowerCase();

				if (this.binders.containsKey(mime)) {
					throw new InitializedException("TODO");
				}

				this.binders.put(mime, binder);
			} else if (type.equals(RESOURCE_ITEM_TYPE)) {
				ResourceItem ri = (ResourceItem) item;

				ResourceMetaData metaData = ResourceMetaDataFactory
						.getInstance().getMetaData(ri.getClazz(), methods);

				if (resource.containsKey(metaData.getKey())) {

					int pre = resource.get(metaData.getKey()).getPrecedence()
							- metaData.getPrecedence();

					if (pre == 0) {
						throw new InitializedException();
					}
					if (pre < 0) {
						metaData = resource.get(metaData.getKey());
					}

				}

				resource.put(metaData.getKey(), metaData);

				if (metaData.isEndpoint()
						&& this.endpoints.contains(metaData.getKey()) == false) {
					this.endpoints.add(metaData.getKey());
				}
			} else if (type.equals(FILTER_ITEM_TYPE)) {
				FilterItem fi = (FilterItem) item;

				Filter f = (Filter) ClassUtils.newInstance(fi.getTarget());

				if (f == null) {
					throw new InitializedException();
				}

				this.filters.add(new DelegateFilter(f, fi.getUrlPattern(), fi
						.getBefore(), fi.getAfter()));
			} else if (type.equals(MESSAGE_ITEM_TYPE)) {
				MessageItem msg = (MessageItem) item;

				for (Enum<?> e : msg.getMessages()) {
					getMessageRepository().getMessage(e);
				}

			}

		}

		this.filters = sortFilter(this.filters);
	}

	public ResourceProxy getEndpoint(String endpoint) {

		for (String str : this.endpoints) {
			ResourceMetaData metaData = this.resource.get(str);

			if (metaData.getPath().equals(endpoint)) {
				return ResourceProxyFactory.getInstance().getProxy(metaData);
			}
		}

		for (String str : this.endpoints) {
			ResourceMetaData metaData = this.resource.get(str);

			if (Pattern.compile(metaData.getPath()).matcher(endpoint).find()) {
				return ResourceProxyFactory.getInstance().getProxy(metaData);
			}
		}

		return null;
	}

	public MethodMetaData getMethodMetaData(String method) {

		for (MethodMetaData metaData : methods) {
			String m = metaData.getMethod();

			if (m.equals(method.toLowerCase())) {
				return metaData;
			}
		}

		return null;
	}

	public void loadMessages() {
		this.messageRepository = MessageManager.getInstance()
				.createRepositpory(messageResources, supportedLocal);
	}

	public void configure(ServletContext servletContext) {

		InputStream is = servletContext.getResourceAsStream(CONFIGURE_XML);

		if (is == null) {

			loadDefaultConfigure();
			return;
		}

		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			factory.setNamespaceAware(false);
			DocumentBuilder builder = factory.newDocumentBuilder();

			Document document = builder.parse(is);

			XPath path = XPathFactory.newInstance().newXPath();
			NodeList list = (NodeList) path.evaluate(
					"/restree/configure/support-locales/locale", document,
					XPathConstants.NODESET);

			for (int i = 0; i < list.getLength(); i++) {
				Node n = list.item(i);
				System.out.println();
				addLocale(n.getTextContent());
			}

			list = (NodeList) path.evaluate(
					"/restree/configure/support-charsets/charset", document,
					XPathConstants.NODESET);

			for (int i = 0; i < list.getLength(); i++) {
				Node n = list.item(i);
				addCharset(n.getTextContent());
			}

			list = (NodeList) path
					.evaluate(
							"/restree/configure/support-request-content-type/content-type",
							document, XPathConstants.NODESET);
			for (int i = 0; i < list.getLength(); i++) {
				Node n = list.item(i);
				addContentType(n.getTextContent(),
						this.supporttedRequestContentType);
			}

			list = (NodeList) path
					.evaluate(
							"/restree/configure/support-response-content-type/content-type",
							document, XPathConstants.NODESET);
			for (int i = 0; i < list.getLength(); i++) {
				Node n = list.item(i);
				addContentType(n.getTextContent(),
						this.supporttedResponseContentType);
			}

			list = (NodeList) path.evaluate(
					"/restree/message-resources/resource", document,
					XPathConstants.NODESET);
			for (int i = 0; i < list.getLength(); i++) {
				Node n = list.item(i);
				addResource(n.getTextContent());
			}

		} catch (ParserConfigurationException e) {
			loadDefaultConfigure();
		} catch (SAXException e) {
			loadDefaultConfigure();
		} catch (IOException e) {
			loadDefaultConfigure();
		} catch (XPathExpressionException e) {
			throw new InitializedException(e);
		}

	}

	private void addContentType(String type, List<String> list) {
		list.add(type);
	}

	private void addLocale(String locale) {
		this.supportedLocal.add(new Locale(locale));
	}

	private void addCharset(String charset) {
		this.supportedCharset.add(charset);
	}

	private void addResource(String res) {
		this.messageResources.add(res);
	}

	private void loadDefaultConfigure() {
		this.supportedLocal = new ArrayList<Locale>();
		this.supportedLocal.add(Locale.getDefault());

		this.supportedCharset = new ArrayList<String>();
		this.supportedCharset.add(System.getProperty("file.encoding"));

		this.supporttedRequestContentType = new ArrayList<String>();
		this.supporttedRequestContentType.add("text/xml");

		this.supporttedResponseContentType = new ArrayList<String>();
		this.supporttedResponseContentType.add("text/xml");
	}

	public Binder getRequestBinder(String mime) {

		if (getSupporttedRequestContentType().contains(mime.toLowerCase())) {
			return this.binders.get(mime.toLowerCase());
		}

		return null;
	}

	public Converter getResponseConverter(String mime) {
		if (getSupporttedResponseContentType().contains(mime.toLowerCase())) {
			return this.converters.get(mime.toLowerCase());
		}

		return null;
	}

	public String getDefaultRequestContentType() {
		return supporttedRequestContentType.get(0);
	}

	public String getDefaultResponseContentType() {
		return supporttedResponseContentType.get(0);
	}

	public String getDefaultCharset() {
		return getSupportedCharset().get(0);
	}

	public Locale getDefaultLocal() {
		return getSupportedLocal().get(0);
	}

}
